using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace GameFramework.Graphics
{
    public class SpriteSheet
    {
        private const int MODULE_ID = 1;
        private const int MODULE_X = 2;
        private const int MODULE_Y = 3;
        private const int MODULE_WIDTH = 4;
        private const int MODULE_HEIGHT = 5;

        private const int ROW_MOD_FRM_ID = 0;
        private const int ROW_MOD_FRM_OX = 1;
        private const int ROW_MOD_FRM_OY = 2;

        private const int ANIM_FRAME_ID = 0;
        private const int ANIM_FRAME_OX = 1;
        private const int ANIM_FRAME_OY = 2;

        #region Fields       
        /// <summary>
        /// source texture of this sprite sheet
        /// </summary>
        private Texture2D _sourceTexture;
        public Texture2D SourceTexture
        {
            get
            {
                return _sourceTexture;
            }
            set
            {
                _sourceTexture = value;
            }
        }

        public int FramesCount
        {
            get
            {
                return _frames.Count;
            }
        }

        private SpriteBatch _spriteBatch;

        private Dictionary<int, Rectangle> _modules;
        private Dictionary<int, FrameModule> _frames;
        private Dictionary<int, AnimationFrames> _animFrames;

        /// <summary>
        /// Use to load resource and unload it when no longer use
        /// </summary>
        private ContentManager _content;

        #endregion

        #region Constructor
        public SpriteSheet(Game game,SpriteBatch spriteBatch, string xmlPath)
        {
            /*init variable*/
            _modules = new Dictionary<int, Rectangle>();//<ID,RECT>
            _frames = new Dictionary<int, FrameModule>();//<ID, frame>
            _animFrames = new Dictionary<int, AnimationFrames>();

            _spriteBatch = spriteBatch;
            /*init the content manager*/
            if(_content == null)
                _content = new ContentManager(game.Services, "Content");

            /*Load Xml file*/
            System.IO.Stream stream = TitleContainer.OpenStream(xmlPath);
            XDocument xDoc;
            try
            {
                xDoc = XDocument.Load(stream);
            }
            catch(Exception e)
            {
                throw e;
            }
            /*read texture path*/
            string texturePath = "";
            foreach(XElement element in xDoc.Descendants("image"))
            {
                texturePath = Convert.ToString(element.Value);
            }            
            /*Load texture*/

            try
            {
                _sourceTexture = _content.Load<Texture2D>(texturePath);
            }
            catch (Exception e)
            {
                throw e;
            }
            /*Load Modules*/
            foreach (XElement moduleElements in xDoc.Descendants("modules"))
            {
                foreach (XElement moduleElement in moduleElements.Elements())
                {
                    string mString = moduleElement.Value as string;
                    string[] nRows = mString.Split(' ');

                    int mID = Convert.ToInt32(nRows[MODULE_ID]);
                    int mX = Convert.ToInt32(nRows[MODULE_X]);
                    int mY = Convert.ToInt32(nRows[MODULE_Y]);
                    int mWidth = Convert.ToInt32(nRows[MODULE_WIDTH]);
                    int mHeight = Convert.ToInt32(nRows[MODULE_HEIGHT]);

                    _modules.Add(mID, new Rectangle(mX, mY, mWidth, mHeight));
                }
            }
            /*Load Frames*/
            foreach (XElement frames in xDoc.Descendants("frames"))
            {
                foreach (XElement itemFrame in frames.Elements())
                {
                    FrameModule frame = new FrameModule();
                    frame.SpriteBatch = _spriteBatch;
                    frame.SourceTexture = _sourceTexture;
                    /*read attributes*/
                    int fID = Convert.ToInt32(itemFrame.Attribute("ID").Value);
                    int fNum = Convert.ToInt32(itemFrame.Attribute("nModFrame").Value);
                    string fName = itemFrame.Attribute("name").Value.ToString();
                    frame.ID = fID;
                    frame.NumOfModuleFrame = fNum;
                    frame.Name = fName;
                    /*read items frame*/
                    foreach(XElement rowMF in itemFrame.Elements())
                    {
                        string strRowMF = rowMF.Value.ToString();
                        string[] arrRowmf = strRowMF.Split(' ');
                        int rID = Convert.ToInt32(arrRowmf[ROW_MOD_FRM_ID]);
                        float rOX = (float)Convert.ToDouble(arrRowmf[ROW_MOD_FRM_OX]);
                        float rOY = (float)Convert.ToDouble(arrRowmf[ROW_MOD_FRM_OY]);
                        frame.Add(_modules[rID], new Vector2(rOX, rOY));
                    }
                    _frames.Add(fID, frame);
                }
               
            }
            /*Load Animations*/
            foreach (XElement anims in xDoc.Descendants("anims"))
            {
                foreach (XElement itemAnim in anims.Elements())
                {
                    AnimationFrames animFrame = new AnimationFrames();                   

                    int aID = Convert.ToInt32(itemAnim.Attribute("ID").Value);
                    int aFrameRate = Convert.ToInt32(itemAnim.Attribute("nAnimFrame").Value);
                    string aName = itemAnim.Attribute("name").Value.ToString();
                    animFrame.Frames = _frames;
                    animFrame.ID = aID;
                    animFrame.FrameRate = aFrameRate;
                    animFrame.Name = aName;

                    foreach (XElement row_frame in itemAnim.Elements())
                    {
                        string strAnimFrame = row_frame.Value.ToString();
                        string[] strAnimFrames = strAnimFrame.Split(' ');

                        int fID = Convert.ToInt32(strAnimFrames[ANIM_FRAME_ID]);
                        float fOX = (float)Convert.ToDouble(strAnimFrames[ANIM_FRAME_OX]);
                        float fOY = (float)Convert.ToDouble(strAnimFrames[ANIM_FRAME_OY]);

                        animFrame.AddNewFrame(fID, new Vector2(fOX, fOY));
                    }
                    _animFrames.Add(aID, animFrame);
                }
            }            
        }     
        #endregion   
   
        //call it when no longer use this object
        public void UnloadContent()
        {
            /*Release all resource that load by this contentmanager*/
            _content.Unload();
            _animFrames.Clear();
            _animFrames = null;
            _frames.Clear();
            _frames = null;
            _modules.Clear();
            _modules = null;
            _sourceTexture = null;
        }

        public FrameModule GetFrameModule(string name)
        {
            foreach (KeyValuePair<int, FrameModule> fm in _frames)
            {
                if (String.Compare(name, fm.Value.Name) == 0)
                {
                    return fm.Value;
                }
            }
            System.Diagnostics.Debug.Assert(true, "can not found this frame");
            return null;
        }

        public void GetFrameModule(string name, out FrameModule frameModule)
        {
            frameModule = new FrameModule();
            foreach (KeyValuePair<int, FrameModule> fm in _frames)
            {
                if (String.Compare(name, fm.Value.Name) == 0)
                {
                    FrameModule original = fm.Value;
                    frameModule.SourceTexture = original.SourceTexture;
                    frameModule.NumOfModuleFrame = original.NumOfModuleFrame;
                    frameModule.SourceRectangle = original.SourceRectangle;
                    frameModule.Offsets = original.Offsets;
                    frameModule.Origin = original.Origin;
                    frameModule.SpriteBatch = original.SpriteBatch;
                    frameModule.Name = original.Name;
                    return;
                }
            }
            System.Diagnostics.Debug.Assert(true, "can not found this frame");
            return;
        }

        public AnimationFrames GetAnimFrames(string name)
        {
            foreach (KeyValuePair<int, AnimationFrames> anim in _animFrames)
            {
                if (String.Compare(name, anim.Value.Name) == 0)
                {
                    return anim.Value;
                }
            }
            System.Diagnostics.Debug.Assert(true, "can not found this anim");
            return null;
        }

        /// <summary>
        /// Insituation you need clone this animation frames
        /// </summary>
        /// <param name="name"></param>
        /// <param name="animFrame"></param>
        public void GetAnimFrames(string name, out AnimationFrames animFrame)
        {
            animFrame = new AnimationFrames();
            
            foreach (KeyValuePair<int, AnimationFrames> anim in _animFrames)
            {
                if (String.Compare(name, anim.Value.Name) == 0)
                {
                    //Clone this Animation frame
                    AnimationFrames original = anim.Value;
                    animFrame.FrameRate = original.FrameRate;                    
                    animFrame.Frames = original.Frames;
                    animFrame.FramesList = original.FramesList;
                    animFrame.Offsets = original.Offsets;
                    return;
                }
            }
            System.Diagnostics.Debug.Assert(true, "can not found this anim");
            return;
        }

        public void UpdateAnim(GameTime gameTime, string animName)
        {
            AnimationFrames anim = GetAnimFrames(animName);
            anim.Update(gameTime);
        }     
    }
}
